package picoevo.es.cmaes;

/**  
 * 
 * CMA-ES Wrapper for PicoEvo.
 * 
 * This is a wrapper for including Niko Hansen CMA Evolution Strategy into the PicoEvo library. CMA-ES code is 
 * completely written by Niko Hansen and unmodified (multi-start version). Modifications from original Niko Hansen's
 * code are:
 * 1. Nearly Only code in this class has been modified from the cma-es example sourcecode to comply with Picoevo requirements.
 * 2. in CMAEvolutionStrategy, "long counteval;" visibility scope was changed to public
 * 3. in CMAEvolutionStrategy, "void init();" visibility scope was changed to public 
 * 4. stableMode flag added so as to override boundaries limitations (which cannot be used in normal mode - sub-optimal implementation)
 * 
 * In Picoevo, CMA-ES is addressed through a World object with performInitialisation and evolve method. It is 
 * possible to use standard ParameterSet objects to pass parameters (compatible with Properties object needed 
 * by cma-es). The proper way to use cma-es in PicoEvo is:
 * (1) copying the (short) main method into your own Object's main method, 
 * (2) write an inherited class from EvaluationOperatorForCMAES (see example provided) and register it (see main method)
 * (3) modify parameters according to your objective function and run.
 * 
 * CMA-ES is developped by Nikolaus Hansen, 1996-today -- for further info about CMA-ES, see CMAEvolutionStrategy.java
 * this wrapper has been written by Nicolas B. starting from TestCMAExample1.java sourcecode provided by Nikolaus Hansen
 *
 */

import picoevo.core.evolution.ParameterSet;
import picoevo.core.representation.World;
import picoevo.es.cmaes.cma.AbstractObjectiveFunction;
import picoevo.es.cmaes.cma.CMAEvolutionStrategy;
import picoevo.es.cmaes.cma.SolutionGeneric;
import picoevo.toolbox.Display;

public class WrapperForCMAES extends World {

	int nbRuns = 1; // restarts, re-read from properties file below
	int dim = 10; // number of variables re-read from properties below
	public SolutionGeneric bestSolution;
	AbstractObjectiveFunction f;
	int it = 0;
	long counteval = 0;
	int irun = 0;
	private boolean _init = false;
	private boolean _stableMode = true;

	// constructors

	public WrapperForCMAES(String __evolutionName, ParameterSet __parameterSet) {
		super(__evolutionName, __parameterSet);
	}

	public WrapperForCMAES(String __evolutionName, String __parameterSetFilename) {
		super(__evolutionName, __parameterSetFilename);
	}

	// methods

	@Override
	public void performInitialisation() {
		nbRuns = 1; // restarts, re-read from properties file below
		dim = 10; // number of variables re-read from properties below
		bestSolution = new SolutionGeneric();
		it = 0;

		this.getTemplate().displayInformation();

		f = (AbstractObjectiveFunction) (this.getTemplate().getObjectProperty("EvaluationOperator"));
		// f = new EmbryoRedFlagTest();

		counteval = 0;
		irun = 0;

		this._init = true;
	}

	@Override
	public void evolve() // mostly copied from Niko Hansen's CMA-ES example
	// sourcecode.
	{
		if (this._init == false)
			Display.critical("must initialize prior to evolving.");

		for (irun = 0; irun < nbRuns; ++irun) {
			CMAEvolutionStrategy cma = new CMAEvolutionStrategy();

			// set stable mode

			cma.setStableMode(_stableMode);

			// setup initialization and options, either reading a file

			// ---

			// [!n] modified code
			cma.setProperties(this.getTemplate());
			dim = (int) Double.parseDouble(this.getTemplate().getProperty("dimension", Integer.toString(dim)));
			nbRuns = (int) Double.parseDouble(this.getTemplate().getProperty("numberOfRuns", Integer.toString(nbRuns)));

			// niko hansen's original code (uncomment all "// ... blabla" lines)
			// ... cma.loadProperties("CMAEvolutionStrategy.properties"); //
			// reads from file cma.propertiesFileName and call cma.setProperty
			// ... Properties props = (ParameterSet) cma.getProperties();

			// ---

			// props.list(System.out);
			// ... dim = (int) Double.parseDouble(props.getProperty("dimension",
			// Integer.toString(dim)));
			// ... nbRuns = (int)
			// Double.parseDouble(props.getProperty("numberOfRuns",
			// Integer.toString(nbRuns)));

			// or using the respective setter functions and options
			// cma.setDimension(dim);
			// cma.setInitialSearchRegion(-0, 5);
			// cma.opts.stopFunctionValue = new Double(1e-7);
			// cma.opts.stopEvaluations = new Long((long) (1e2 + 3e4*dim*dim));

			// cma.parameters.setCcov(0.0); // for "CSA"

			// cma.opts.stopTolXFactor = new Double(0e-7);
			// cma.opts.stopTolX = new Double(0e-7);
			// cma.opts.lowerStdDev = new double[]{1e-4, 1e-8};
			// cma.opts.stopTolFun = new Double(1e-8);
			// cma.opts.stopTolFunHist = new Double(-1e-8);

			if (irun == 0) {
				cma.writeToDefaultFilesHeaders();
			} else {
				cma.opts.flgAppendFiles = true; // append output files
				cma.parameters.setPopulationSize((int) (cma.parameters.getPopulationSize() * Math.pow(2, irun)));
				cma.init();
				cma.counteval = counteval; // yet a real hack TODO
			}

			if (nbRuns > 1) // if multistarts also use iterations as termination
				// criterion
				cma.opts.stopIterations = new Long((long) (1e2 + 100 * dim * dim * Math.sqrt(cma.parameters.getLambda())));

			// Generation loop

			double lastCountEval = 0, waitCountEval = 0;
			double lastTime = 0, llastTime = 0;
			System.out.println(cma.printHeader());
			while (cma.stopConditions.isFalse()) {

				it++;

				double[][] pop = cma.samplePopulation(); // first index:
				// individual,
				// second index:
				// variable
				double[] fitness;

				fitness = f.valueOf(pop); // parallel evaluation of
				// pop[0]..pop[lambda-1]

				cma.updateDistribution(fitness);

				// the remainder is output
				// file output
				if (cma.stopConditions.isTrue() || cma.getCountEval() >= lastCountEval + waitCountEval) {
					cma.writeToDefaultFilesNew();
					lastCountEval = cma.getCountEval();
					waitCountEval = waitCountEval + 1;
				}
				// screen output
				if (System.currentTimeMillis() - llastTime > 20000) {
					cma.printlnCaption(System.out);
					llastTime = System.currentTimeMillis();
				}
				if (cma.stopConditions.isTrue() || cma.getCountIter() < 4 || System.currentTimeMillis() - lastTime > 2500) {
					cma.println(System.out);
					lastTime = System.currentTimeMillis();
				}
			} // generation loop

			// evaluate f.valueOf(cma.getMeanX()) here, because it might be
			// better than cma.getBestX()?
			// retain best solution found so far here!?
			if (cma.getBestSolution().getFunctionValue() < bestSolution.getFunctionValue())
				bestSolution = (SolutionGeneric) cma.getBestSolution();

			counteval = cma.getCountEval(); // keep number of f-evals
			System.out.println("Stop in cma: '" + cma.stopConditions.getStrings()[0] + "'");

			// quit loop given the following termination conditions are
			// satisfied, otherwise continue
			if (cma.stopConditions.getStrings()[0].substring(0, 11).compareTo("Evaluations") == 0 || cma.stopConditions.getStrings()[0].substring(0, 13).compareTo("FunctionValue") == 0) {
				++irun;
				break;
			}
		} // for irun < nbRuns
		// if (irun > 1)
		// {
		System.out.println(" " + (irun) + " runs conducted.");
		System.out.println(" best function value " + bestSolution.getFunctionValue() + " at evaluation " + bestSolution.getEvaluation());
		System.out.print(" best genome : \n { ");
		double[] genome = bestSolution.getX();
		for (int i = 0; i != genome.length; i++) {
			System.out.print("" + genome[i] + " ");
			if (i != genome.length - 1)
				System.out.print(", ");
		}
		System.out.println("};");
		// }
	} // end evolve()

	public boolean isStableMode() {
		return _stableMode;
	}

	/**
	 * the only use of this method is to override the stable mode in cma-es.
	 * With stable mode at true (default value), it is not possible to use the
	 * boundaries options. Note that current boundaries implementation (as of
	 * 2007/02) is known to be sub-optimal and should end up being rewritten in
	 * the future.
	 * 
	 * @param mode
	 */
	public void setStableMode(boolean mode) {
		_stableMode = mode;
	}

	/*
	 * --- example code -- see picoevo.tutorials.cmaes
	 * 
	 * public static void main(String[] args) { // ### STEP 1 : setting
	 * parameters/properties (choose either method 1 (load filename) or 2
	 * (manual setting) )
	 * 
	 * ParameterSet params = new ParameterSet("CMA-ES ParameterSet");
	 * params.setProperty("EvaluationOperator",new
	 * SphereFunctionEvaluationOperator()); // evaluation operator must be
	 * defined as an AbstractObjectiveFunction (nikko hansen's cma-es
	 * requirements) // ## method 1 (loading properties from file)
	 * //params.loadProperties("picoevo/tutorials/cmaes/CMAEvolutionStrategy.properties"); // ##
	 * method 2 (setting up properties manually) //--- // #--- General ---
	 * params.setProperty("dimension","20"); // #--- Boundary, in case lower
	 * *and* upper boundary must be set
	 * //params.setProperty("boundaryLower","-10.0");
	 * //params.setProperty("boundaryUpper","10.0"); // #---Initialisation: X
	 * and StandardDeviation have precedence
	 * params.setProperty("initialSearchRegionLower","-1.0"); // # 1 value or
	 * dimension values params.setProperty("initialSearchRegionUpper","0.0"); // #
	 * ditto //params.setProperty("!initialX","1"); // # 1 value or dimension
	 * values //params.setProperty("!initialStandardDeviations","0.5"); // # 1
	 * value or dimension values // #--- Termination
	 * params.setProperty("stopFunctionValue","1e-3");
	 * params.setProperty("stopTolFun","1e-12");
	 * params.setProperty("stopTolFunHist","1e-12");
	 * params.setProperty("stopTolX","0.0"); // # absolute steps
	 * params.setProperty("stopTolXFactor","1e-9"); // # relative step-size
	 * reduction params.setProperty("stopTolUpXFactor","1000");
	 * //params.setProperty("stopEvaluations","33");
	 * //params.setProperty("stopIterations","10"); // #--- Strategy parameters
	 * //params.setProperty("populationSize","10"); // # AKA lambda - mu is
	 * automaticaly computed as lambda/2 // #--- Various
	 * params.setProperty("numberOfRuns","12"); // restart mode if > 1
	 * //params.setProperty("lowerStandardDeviations","0"); // # last number is
	 * recycled up to dimension
	 * //params.setProperty("upperStandardDeviations","1"); // # last number is
	 * recycled up to dimension
	 * params.setProperty("maximumTimeFractionForEigendecomposition","0.5"); //
	 * !!! a smaller fraction, e.g. 0.2, may be faster in terms of overall
	 * CPU-time: // --- // ## log files String logfilenameprefix =
	 * "log/logfile_cmaes_testing_"+Misc.getCurrentTimeAsCompactString();
	 * params.displayInformation(logfilenameprefix+".param");
	 * params.setProperty("outputFileNamesPrefix",logfilenameprefix+"_"); // ###
	 * STEP 2 : INITIALISING and EVOLVING
	 * 
	 * WrapperForCMAES myEvolvableWorld = new
	 * WrapperForCMAES("EvolvingWithCMA-ES",params);
	 * myEvolvableWorld.performInitialisation(); myEvolvableWorld.evolve(); //
	 * perform full evolution (with restart, if needed) }
	 */

} // class
